{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "07_pytorch_experiment_tracking_exercise_template.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "toc_visible": true,
      "authorship_tag": "ABX9TyP4+Nwb43yrG43qNz11d5C4",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU",
    "gpuClass": "standard"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/mrdbourke/pytorch-deep-learning/blob/main/extras/exercises/07_pytorch_experiment_tracking_exercise_template.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 07. PyTorch Experiment Tracking Exercise Template\n",
        "\n",
        "Welcome to the 07. PyTorch Experiment Tracking exercise template notebook.\n",
        "\n",
        "> **Note:** There may be more than one solution to each of the exercises. This notebook only shows one possible example.\n",
        "\n",
        "## Resources\n",
        "\n",
        "1. These exercises/solutions are based on [section 07. PyTorch Transfer Learning](https://www.learnpytorch.io/07_pytorch_experiment_tracking/) of the Learn PyTorch for Deep Learning course by Zero to Mastery.\n",
        "2. See a live [walkthrough of the solutions (errors and all) on YouTube](https://youtu.be/cO_r2FYcAjU).\n",
        "3. See [other solutions on the course GitHub](https://github.com/mrdbourke/pytorch-deep-learning/tree/main/extras/solutions).\n",
        "\n",
        "> **Note:** The first section of this notebook is dedicated to getting various helper functions and datasets used for the exercises. The exercises start at the heading \"Exercise 1: ...\"."
      ],
      "metadata": {
        "id": "zNqPNlYylluR"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Get various imports and helper functions\n",
        "\n",
        "We'll need to make sure we have `torch` v.1.12+ and `torchvision` v0.13+."
      ],
      "metadata": {
        "id": "sf8ab9cyHTzU"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# For this notebook to run with updated APIs, we need torch 1.12+ and torchvision 0.13+\n",
        "try:\n",
        "    import torch\n",
        "    import torchvision\n",
        "    assert int(torch.__version__.split(\".\")[1]) >= 12, \"torch version should be 1.12+\"\n",
        "    assert int(torchvision.__version__.split(\".\")[1]) >= 13, \"torchvision version should be 0.13+\"\n",
        "    print(f\"torch version: {torch.__version__}\")\n",
        "    print(f\"torchvision version: {torchvision.__version__}\")\n",
        "except:\n",
        "    print(f\"[INFO] torch/torchvision versions not as required, installing nightly versions.\")\n",
        "    !pip3 install -U --pre torch torchvision --extra-index-url https://download.pytorch.org/whl/nightly/cu113\n",
        "    import torch\n",
        "    import torchvision\n",
        "    print(f\"torch version: {torch.__version__}\")\n",
        "    print(f\"torchvision version: {torchvision.__version__}\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5MOv1De4mxeL",
        "outputId": "a4f9fdd9-f225-4861-e204-e26186365bea"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "torch version: 1.13.0.dev20220622+cu113\n",
            "torchvision version: 0.14.0.dev20220622+cu113\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        " # Make sure we have a GPU\n",
        " device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
        " device"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        },
        "id": "Nf-DsrZipCE9",
        "outputId": "f4cc7d1c-da78-4eb4-c753-4e135771650c"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'cuda'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 2
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Get regular imports \n",
        "import matplotlib.pyplot as plt\n",
        "import torch\n",
        "import torchvision\n",
        "\n",
        "from torch import nn\n",
        "from torchvision import transforms\n",
        "\n",
        "# Try to get torchinfo, install it if it doesn't work\n",
        "try:\n",
        "    from torchinfo import summary\n",
        "except:\n",
        "    print(\"[INFO] Couldn't find torchinfo... installing it.\")\n",
        "    !pip install -q torchinfo\n",
        "    from torchinfo import summary\n",
        "\n",
        "# Try to import the going_modular directory, download it from GitHub if it doesn't work\n",
        "try:\n",
        "    from going_modular.going_modular import data_setup, engine\n",
        "except:\n",
        "    # Get the going_modular scripts\n",
        "    print(\"[INFO] Couldn't find going_modular scripts... downloading them from GitHub.\")\n",
        "    !git clone https://github.com/mrdbourke/pytorch-deep-learning\n",
        "    !mv pytorch-deep-learning/going_modular .\n",
        "    !rm -rf pytorch-deep-learning\n",
        "    from going_modular.going_modular import data_setup, engine"
      ],
      "metadata": {
        "id": "i_52puIeoab3"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Set seeds\n",
        "def set_seeds(seed: int=42):\n",
        "    \"\"\"Sets random sets for torch operations.\n",
        "\n",
        "    Args:\n",
        "        seed (int, optional): Random seed to set. Defaults to 42.\n",
        "    \"\"\"\n",
        "    # Set the seed for general torch operations\n",
        "    torch.manual_seed(seed)\n",
        "    # Set the seed for CUDA torch operations (ones that happen on the GPU)\n",
        "    torch.cuda.manual_seed(seed)"
      ],
      "metadata": {
        "id": "DBj8I3P9pNK2"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "import os\n",
        "import zipfile\n",
        "\n",
        "from pathlib import Path\n",
        "\n",
        "import requests\n",
        "\n",
        "def download_data(source: str, \n",
        "                  destination: str,\n",
        "                  remove_source: bool = True) -> Path:\n",
        "    \"\"\"Downloads a zipped dataset from source and unzips to destination.\n",
        "\n",
        "    Args:\n",
        "        source (str): A link to a zipped file containing data.\n",
        "        destination (str): A target directory to unzip data to.\n",
        "        remove_source (bool): Whether to remove the source after downloading and extracting.\n",
        "    \n",
        "    Returns:\n",
        "        pathlib.Path to downloaded data.\n",
        "    \n",
        "    Example usage:\n",
        "        download_data(source=\"https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip\",\n",
        "                      destination=\"pizza_steak_sushi\")\n",
        "    \"\"\"\n",
        "    # Setup path to data folder\n",
        "    data_path = Path(\"data/\")\n",
        "    image_path = data_path / destination\n",
        "\n",
        "    # If the image folder doesn't exist, download it and prepare it... \n",
        "    if image_path.is_dir():\n",
        "        print(f\"[INFO] {image_path} directory exists, skipping download.\")\n",
        "    else:\n",
        "        print(f\"[INFO] Did not find {image_path} directory, creating one...\")\n",
        "        image_path.mkdir(parents=True, exist_ok=True)\n",
        "        \n",
        "        # Download pizza, steak, sushi data\n",
        "        target_file = Path(source).name\n",
        "        with open(data_path / target_file, \"wb\") as f:\n",
        "            request = requests.get(source)\n",
        "            print(f\"[INFO] Downloading {target_file} from {source}...\")\n",
        "            f.write(request.content)\n",
        "\n",
        "        # Unzip pizza, steak, sushi data\n",
        "        with zipfile.ZipFile(data_path / target_file, \"r\") as zip_ref:\n",
        "            print(f\"[INFO] Unzipping {target_file} data...\") \n",
        "            zip_ref.extractall(image_path)\n",
        "\n",
        "        # Remove .zip file\n",
        "        if remove_source:\n",
        "            os.remove(data_path / target_file)\n",
        "    \n",
        "    return image_path\n",
        "\n",
        "image_path = download_data(source=\"https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip\",\n",
        "                           destination=\"pizza_steak_sushi\")\n",
        "image_path"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "m6R-CS53pTLS",
        "outputId": "3f7688b7-0b86-4cd8-bb11-4e71f8e7270f"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[INFO] data/pizza_steak_sushi directory exists, skipping download.\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "PosixPath('data/pizza_steak_sushi')"
            ]
          },
          "metadata": {},
          "execution_count": 6
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from torch.utils.tensorboard import SummaryWriter\n",
        "def create_writer(experiment_name: str, \n",
        "                  model_name: str, \n",
        "                  extra: str=None):\n",
        "    \"\"\"Creates a torch.utils.tensorboard.writer.SummaryWriter() instance saving to a specific log_dir.\n",
        "\n",
        "    log_dir is a combination of runs/timestamp/experiment_name/model_name/extra.\n",
        "\n",
        "    Where timestamp is the current date in YYYY-MM-DD format.\n",
        "\n",
        "    Args:\n",
        "        experiment_name (str): Name of experiment.\n",
        "        model_name (str): Name of model.\n",
        "        extra (str, optional): Anything extra to add to the directory. Defaults to None.\n",
        "\n",
        "    Returns:\n",
        "        torch.utils.tensorboard.writer.SummaryWriter(): Instance of a writer saving to log_dir.\n",
        "\n",
        "    Example usage:\n",
        "        # Create a writer saving to \"runs/2022-06-04/data_10_percent/effnetb2/5_epochs/\"\n",
        "        writer = create_writer(experiment_name=\"data_10_percent\",\n",
        "                               model_name=\"effnetb2\",\n",
        "                               extra=\"5_epochs\")\n",
        "        # The above is the same as:\n",
        "        writer = SummaryWriter(log_dir=\"runs/2022-06-04/data_10_percent/effnetb2/5_epochs/\")\n",
        "    \"\"\"\n",
        "    from datetime import datetime\n",
        "    import os\n",
        "\n",
        "    # Get timestamp of current date (all experiments on certain day live in same folder)\n",
        "    timestamp = datetime.now().strftime(\"%Y-%m-%d\") # returns current date in YYYY-MM-DD format\n",
        "\n",
        "    if extra:\n",
        "        # Create log directory path\n",
        "        log_dir = os.path.join(\"runs\", timestamp, experiment_name, model_name, extra)\n",
        "    else:\n",
        "        log_dir = os.path.join(\"runs\", timestamp, experiment_name, model_name)\n",
        "        \n",
        "    print(f\"[INFO] Created SummaryWriter, saving to: {log_dir}...\")\n",
        "    return SummaryWriter(log_dir=log_dir)"
      ],
      "metadata": {
        "id": "BE60IEEkr89l"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Create a test writer\n",
        "writer = create_writer(experiment_name=\"test_experiment_name\",\n",
        "                       model_name=\"this_is_the_model_name\",\n",
        "                       extra=\"add_a_little_extra_if_you_want\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "S0BH4ONGsgNB",
        "outputId": "077f7b39-f4d0-44dd-b74f-8ead04fb7add"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[INFO] Created SummaryWriter, saving to: runs/2022-06-23/test_experiment_name/this_is_the_model_name/add_a_little_extra_if_you_want...\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from typing import Dict, List\n",
        "from tqdm.auto import tqdm\n",
        "\n",
        "from going_modular.going_modular.engine import train_step, test_step\n",
        "\n",
        "# Add writer parameter to train()\n",
        "def train(model: torch.nn.Module, \n",
        "          train_dataloader: torch.utils.data.DataLoader, \n",
        "          test_dataloader: torch.utils.data.DataLoader, \n",
        "          optimizer: torch.optim.Optimizer,\n",
        "          loss_fn: torch.nn.Module,\n",
        "          epochs: int,\n",
        "          device: torch.device, \n",
        "          writer: torch.utils.tensorboard.writer.SummaryWriter # new parameter to take in a writer\n",
        "          ) -> Dict[str, List]:\n",
        "    \"\"\"Trains and tests a PyTorch model.\n",
        "\n",
        "    Passes a target PyTorch models through train_step() and test_step()\n",
        "    functions for a number of epochs, training and testing the model\n",
        "    in the same epoch loop.\n",
        "\n",
        "    Calculates, prints and stores evaluation metrics throughout.\n",
        "\n",
        "    Stores metrics to specified writer log_dir if present.\n",
        "\n",
        "    Args:\n",
        "      model: A PyTorch model to be trained and tested.\n",
        "      train_dataloader: A DataLoader instance for the model to be trained on.\n",
        "      test_dataloader: A DataLoader instance for the model to be tested on.\n",
        "      optimizer: A PyTorch optimizer to help minimize the loss function.\n",
        "      loss_fn: A PyTorch loss function to calculate loss on both datasets.\n",
        "      epochs: An integer indicating how many epochs to train for.\n",
        "      device: A target device to compute on (e.g. \"cuda\" or \"cpu\").\n",
        "      writer: A SummaryWriter() instance to log model results to.\n",
        "\n",
        "    Returns:\n",
        "      A dictionary of training and testing loss as well as training and\n",
        "      testing accuracy metrics. Each metric has a value in a list for \n",
        "      each epoch.\n",
        "      In the form: {train_loss: [...],\n",
        "                train_acc: [...],\n",
        "                test_loss: [...],\n",
        "                test_acc: [...]} \n",
        "      For example if training for epochs=2: \n",
        "              {train_loss: [2.0616, 1.0537],\n",
        "                train_acc: [0.3945, 0.3945],\n",
        "                test_loss: [1.2641, 1.5706],\n",
        "                test_acc: [0.3400, 0.2973]} \n",
        "    \"\"\"\n",
        "    # Create empty results dictionary\n",
        "    results = {\"train_loss\": [],\n",
        "               \"train_acc\": [],\n",
        "               \"test_loss\": [],\n",
        "               \"test_acc\": []\n",
        "    }\n",
        "\n",
        "    # Loop through training and testing steps for a number of epochs\n",
        "    for epoch in tqdm(range(epochs)):\n",
        "        train_loss, train_acc = train_step(model=model,\n",
        "                                          dataloader=train_dataloader,\n",
        "                                          loss_fn=loss_fn,\n",
        "                                          optimizer=optimizer,\n",
        "                                          device=device)\n",
        "        test_loss, test_acc = test_step(model=model,\n",
        "          dataloader=test_dataloader,\n",
        "          loss_fn=loss_fn,\n",
        "          device=device)\n",
        "\n",
        "        # Print out what's happening\n",
        "        print(\n",
        "          f\"Epoch: {epoch+1} | \"\n",
        "          f\"train_loss: {train_loss:.4f} | \"\n",
        "          f\"train_acc: {train_acc:.4f} | \"\n",
        "          f\"test_loss: {test_loss:.4f} | \"\n",
        "          f\"test_acc: {test_acc:.4f}\"\n",
        "        )\n",
        "\n",
        "        # Update results dictionary\n",
        "        results[\"train_loss\"].append(train_loss)\n",
        "        results[\"train_acc\"].append(train_acc)\n",
        "        results[\"test_loss\"].append(test_loss)\n",
        "        results[\"test_acc\"].append(test_acc)\n",
        "\n",
        "\n",
        "        ### New: Use the writer parameter to track experiments ###\n",
        "        # See if there's a writer, if so, log to it\n",
        "        if writer:\n",
        "            # Add results to SummaryWriter\n",
        "            writer.add_scalars(main_tag=\"Loss\", \n",
        "                               tag_scalar_dict={\"train_loss\": train_loss,\n",
        "                                                \"test_loss\": test_loss},\n",
        "                               global_step=epoch)\n",
        "            writer.add_scalars(main_tag=\"Accuracy\", \n",
        "                               tag_scalar_dict={\"train_acc\": train_acc,\n",
        "                                                \"test_acc\": test_acc}, \n",
        "                               global_step=epoch)\n",
        "\n",
        "            # Close the writer\n",
        "            writer.close()\n",
        "        else:\n",
        "            pass\n",
        "    ### End new ###\n",
        "\n",
        "    # Return the filled results at the end of the epochs\n",
        "    return results"
      ],
      "metadata": {
        "id": "VwO0Q1eFsusV"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Download data\n",
        "\n",
        "Using the same data from https://www.learnpytorch.io/07_pytorch_experiment_tracking/"
      ],
      "metadata": {
        "id": "nh8jKzHYHYL3"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Download 10 percent and 20 percent training data (if necessary)\n",
        "data_10_percent_path = download_data(source=\"https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip\",\n",
        "                                     destination=\"pizza_steak_sushi\")\n",
        "\n",
        "data_20_percent_path = download_data(source=\"https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi_20_percent.zip\",\n",
        "                                     destination=\"pizza_steak_sushi_20_percent\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "68QGCR_1tzif",
        "outputId": "a5073b19-1463-4d8a-ec08-5399945196a4"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[INFO] data/pizza_steak_sushi directory exists, skipping download.\n",
            "[INFO] data/pizza_steak_sushi_20_percent directory exists, skipping download.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Setup training directory paths\n",
        "train_dir_10_percent = data_10_percent_path / \"train\"\n",
        "train_dir_20_percent = data_20_percent_path / \"train\"\n",
        "\n",
        "# Setup testing directory paths (note: use the same test dataset for both to compare the results)\n",
        "test_dir = data_10_percent_path / \"test\"\n",
        "\n",
        "# Check the directories\n",
        "print(f\"Training directory 10%: {train_dir_10_percent}\")\n",
        "print(f\"Training directory 20%: {train_dir_20_percent}\")\n",
        "print(f\"Testing directory: {test_dir}\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9L2rCRxvt1ED",
        "outputId": "30b8202e-96f3-443f-e89e-2a83e11b1c85"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Training directory 10%: data/pizza_steak_sushi/train\n",
            "Training directory 20%: data/pizza_steak_sushi_20_percent/train\n",
            "Testing directory: data/pizza_steak_sushi/test\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from torchvision import transforms\n",
        "\n",
        "# Create a transform to normalize data distribution to be inline with ImageNet\n",
        "normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], # values per colour channel [red, green, blue]\n",
        "                                 std=[0.229, 0.224, 0.225])\n",
        "\n",
        "# Create a transform pipeline\n",
        "simple_transform = transforms.Compose([\n",
        "                                       transforms.Resize((224, 224)),\n",
        "                                       transforms.ToTensor(), # get image values between 0 & 1\n",
        "                                       normalize\n",
        "])"
      ],
      "metadata": {
        "id": "K35q9wswt6NH"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Turn data into DataLoaders "
      ],
      "metadata": {
        "id": "SBuEla8pHea9"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "BATCH_SIZE = 32\n",
        "\n",
        "# Create 10% training and test DataLoaders\n",
        "train_dataloader_10_percent, test_dataloader, class_names = data_setup.create_dataloaders(train_dir=train_dir_10_percent,\n",
        "                                                                                          test_dir=test_dir,\n",
        "                                                                                          transform=simple_transform,\n",
        "                                                                                          batch_size=BATCH_SIZE)\n",
        "\n",
        "# Create 20% training and test DataLoaders\n",
        "train_dataloader_20_percent, test_dataloader, class_names = data_setup.create_dataloaders(train_dir=train_dir_20_percent,\n",
        "                                                                                          test_dir=test_dir,\n",
        "                                                                                          transform=simple_transform,\n",
        "                                                                                          batch_size=BATCH_SIZE)\n",
        "\n",
        "# Find the number of samples/batches per dataloader (using the same test_dataloader for both experiments)\n",
        "print(f\"Number of batches of size {BATCH_SIZE} in 10 percent training data: {len(train_dataloader_10_percent)}\")\n",
        "print(f\"Number of batches of size {BATCH_SIZE} in 20 percent training data: {len(train_dataloader_20_percent)}\")\n",
        "print(f\"Number of batches of size {BATCH_SIZE} in testing data: {len(train_dataloader_10_percent)} (all experiments will use the same test set)\")\n",
        "print(f\"Number of classes: {len(class_names)}, class names: {class_names}\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "xlQU94HBuqOq",
        "outputId": "db4b9144-fd3b-4f31-e0c1-ebd60f906232"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Number of batches of size 32 in 10 percent training data: 8\n",
            "Number of batches of size 32 in 20 percent training data: 15\n",
            "Number of batches of size 32 in testing data: 8 (all experiments will use the same test set)\n",
            "Number of classes: 3, class names: ['pizza', 'steak', 'sushi']\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Exercise 1: Pick a larger model from [`torchvision.models`](https://pytorch.org/vision/main/models.html) to add to the list of experiments (for example, EffNetB3 or higher)\n",
        "\n",
        "* How does it perform compared to our existing models?\n",
        "* **Hint:** You'll need to set up an exerpiment similar to [07. PyTorch Experiment Tracking section 7.6](https://www.learnpytorch.io/07_pytorch_experiment_tracking/#76-create-experiments-and-set-up-training-code)."
      ],
      "metadata": {
        "id": "nwmoMhW8IqSu"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# TODO: your code"
      ],
      "metadata": {
        "id": "F-35y0uxJ8tg"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Exercise 2. Introduce data augmentation to the list of experiments using the 20% pizza, steak, sushi training and test datasets, does this change anything?\n",
        "    \n",
        "* For example, you could have one training DataLoader that uses data augmentation (e.g. `train_dataloader_20_percent_aug` and `train_dataloader_20_percent_no_aug`) and then compare the results of two of the same model types training on these two DataLoaders.\n",
        "* **Note:** You may need to alter the `create_dataloaders()` function to be able to take a transform for the training data and the testing data (because you don't need to perform data augmentation on the test data). See [04. PyTorch Custom Datasets section 6](https://www.learnpytorch.io/04_pytorch_custom_datasets/#6-other-forms-of-transforms-data-augmentation) for examples of using data augmentation or the script below for an example:\n",
        "\n",
        "```python\n",
        "# Note: Data augmentation transform like this should only be performed on training data\n",
        "train_transform_data_aug = transforms.Compose([\n",
        "    transforms.Resize((224, 224)),\n",
        "    transforms.TrivialAugmentWide(),\n",
        "    transforms.ToTensor(),\n",
        "    normalize\n",
        "])\n",
        "\n",
        "# Create a helper function to visualize different augmented (and not augmented) images\n",
        "def view_dataloader_images(dataloader, n=10):\n",
        "    if n > 10:\n",
        "        print(f\"Having n higher than 10 will create messy plots, lowering to 10.\")\n",
        "        n = 10\n",
        "    imgs, labels = next(iter(dataloader))\n",
        "    plt.figure(figsize=(16, 8))\n",
        "    for i in range(n):\n",
        "        # Min max scale the image for display purposes\n",
        "        targ_image = imgs[i]\n",
        "        sample_min, sample_max = targ_image.min(), targ_image.max()\n",
        "        sample_scaled = (targ_image - sample_min)/(sample_max - sample_min)\n",
        "\n",
        "        # Plot images with appropriate axes information\n",
        "        plt.subplot(1, 10, i+1)\n",
        "        plt.imshow(sample_scaled.permute(1, 2, 0)) # resize for Matplotlib requirements\n",
        "        plt.title(class_names[labels[i]])\n",
        "        plt.axis(False)\n",
        "\n",
        "# Have to update `create_dataloaders()` to handle different augmentations\n",
        "import os\n",
        "from torch.utils.data import DataLoader\n",
        "from torchvision import datasets\n",
        "\n",
        "NUM_WORKERS = os.cpu_count() # use maximum number of CPUs for workers to load data \n",
        "\n",
        "# Note: this is an update version of data_setup.create_dataloaders to handle\n",
        "# differnt train and test transforms.\n",
        "def create_dataloaders(\n",
        "    train_dir, \n",
        "    test_dir, \n",
        "    train_transform, # add parameter for train transform (transforms on train dataset)\n",
        "    test_transform,  # add parameter for test transform (transforms on test dataset)\n",
        "    batch_size=32, num_workers=NUM_WORKERS\n",
        "):\n",
        "    # Use ImageFolder to create dataset(s)\n",
        "    train_data = datasets.ImageFolder(train_dir, transform=train_transform)\n",
        "    test_data = datasets.ImageFolder(test_dir, transform=test_transform)\n",
        "\n",
        "    # Get class names\n",
        "    class_names = train_data.classes\n",
        "\n",
        "    # Turn images into data loaders\n",
        "    train_dataloader = DataLoader(\n",
        "        train_data,\n",
        "        batch_size=batch_size,\n",
        "        shuffle=True,\n",
        "        num_workers=num_workers,\n",
        "        pin_memory=True,\n",
        "    )\n",
        "    test_dataloader = DataLoader(\n",
        "        test_data,\n",
        "        batch_size=batch_size,\n",
        "        shuffle=True,\n",
        "        num_workers=num_workers,\n",
        "        pin_memory=True,\n",
        "    )\n",
        "\n",
        "    return train_dataloader, test_dataloader, class_names\n",
        "```"
      ],
      "metadata": {
        "id": "YqlStPo-gbrF"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# TODO: your code"
      ],
      "metadata": {
        "id": "E1N3yyDOoH2t"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Exercise 3. Scale up the dataset to turn FoodVision Mini into FoodVision Big using the entire [Food101 dataset from `torchvision.models`](https://pytorch.org/vision/stable/generated/torchvision.datasets.Food101.html#torchvision.datasets.Food101)\n",
        "    \n",
        "* You could take the best performing model from your various experiments or even the EffNetB2 feature extractor we created in this notebook and see how it goes fitting for 5 epochs on all of Food101.\n",
        "* If you try more than one model, it would be good to have the model's results tracked.\n",
        "* If you load the Food101 dataset from `torchvision.models`, you'll have to create PyTorch DataLoaders to use it in training.\n",
        "* **Note:** Due to the larger amount of data in Food101 compared to our pizza, steak, sushi dataset, this model will take longer to train."
      ],
      "metadata": {
        "id": "1IvuTskxgjaw"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# TODO: your code"
      ],
      "metadata": {
        "id": "YehliYnYoP1x"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}
